﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Globalization;
using System.Windows.Documents;
using ShredTheLog.Api;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Collections;
using System.Windows.Controls;
using System.Windows;
using ShredTheLog.Api.DataContracts.TextChat;

namespace ShredTheLog.ViewModels
{
    [ValueConversion(typeof(IList<IMessage>), typeof(FlowDocument))]
    public sealed class MessagesConverter : IValueConverter
    {
        [Import]
        private IShredServices ShredServices { get; set; }

        [ImportMany]
        private Lazy<IMessageFormatter, IOrderMetadata>[] _formatters = null;
        private IEnumerable<IMessageFormatter> Formatters
        {
            get 
            {
                Contract.Assume(_formatters != null);

                return from i in _formatters orderby i.Metadata.Order select i.Value; 
            }
        }

        public MessagesConverter()
        {
            Globals.CompositionContainer.SatisfyImportsOnce(this);
            ShredServices.SettingsUpdated +=
                (sender, e) =>
                {
                    Trace.WriteLineIf(TraceFlags.MessagesConverter, String.Format("{0}: Cached documents count: {1:d}.", this.GetType().Name, _cachedDocs.Count));
                    _cachedDocs.Clear();
                    Trace.WriteLineIf(TraceFlags.MessagesConverter, String.Format("{0}: Cached documents count: {1:d}.", this.GetType().Name, _cachedDocs.Count));
                };
        }

        private Dictionary<IList<IMessage>, DocDescriptor> _cachedDocs = new Dictionary<IList<IMessage>, DocDescriptor>();

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Trace.WriteLineIf(TraceFlags.MessagesConverter, String.Format("{0}: Cached documents count: {1:d}.", this.GetType().Name, _cachedDocs.Count));
            var messages = (IList<IMessage>)value;
            Contract.Assume(messages != null);

            var entriesToRemove =
                from i in _cachedDocs
                where i.Value.Document.Parent != null && !Window.GetWindow(i.Value.Document.Parent).IsVisible
                select i.Key;
            entriesToRemove.ToArray().ForEach(i => _cachedDocs.Remove(i));
            Trace.WriteLineIf(TraceFlags.MessagesConverter, String.Format("{0}: Cached documents count: {1:d}.", this.GetType().Name, _cachedDocs.Count));

            DocDescriptor doc;
            if (!_cachedDocs.TryGetValue(messages, out doc))
            {
                Trace.WriteLineIf(TraceFlags.MessagesConverter, "Caching doc...");
                doc = new DocDescriptor();
                _cachedDocs[messages] = doc;
            }

            var paragraph = (Paragraph)doc.Document.Blocks.FirstBlock;
            var inlines = messages.Skip(doc.MessageCount).SelectMany(
                (mv, i) =>
                {
                    if (i != 0 || paragraph.Inlines.Count != 0)
                        return new Inline[] { new LineBreak(), CreateSpan(mv) };
                    else
                        return new Inline[] { CreateSpan(mv) };
                });
            paragraph.Inlines.AddRange(inlines);
            doc.MessageCount = messages.Count;
            Trace.WriteLineIf(TraceFlags.MessagesConverter, String.Format("{0}: Cached documents count: {1:d}.", this.GetType().Name, _cachedDocs.Count));
            return doc.Document;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }

        private Span CreateSpan(IMessage message)
        {
            Contract.Requires(message != null);

            var embracingSpan = new Span();
            embracingSpan.DataContext = message;
            var userNameRun = new Run(String.Format(CultureInfo.CurrentCulture, "{0}: ", message.FirstName));
            var textRun = new Run(message.Text);
            var userNameSpan = new NameSpan(userNameRun);
            var textSpan = new MessageSpan(textRun);
            embracingSpan.Inlines.Add(userNameSpan);
            embracingSpan.Inlines.Add(textSpan);
            Formatters.ForEach(mf => mf.Process(message, userNameSpan, textSpan, embracingSpan));
            return embracingSpan;
        }

        sealed class DocDescriptor
        {
            public DocDescriptor()
            {
                Document = new FlowDocument(new Paragraph()) { TextAlignment = TextAlignment.Left };
            }

            public FlowDocument Document { get; private set; }
            public int MessageCount { get; set; }
        }

        public static readonly MessagesConverter Default = new MessagesConverter();
    }

    public class NameSpan : Span 
    {
        public NameSpan(Inline childInline)
            : base(childInline)
        {
        }
    }

    public class MessageSpan : Span
    { 
        public MessageSpan(Inline childInline)
            : base(childInline)
        {
        }
    }
}
